home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / answers / unix-faq / shell / csh-whynot next >
Encoding:
Text File  |  1993-02-11  |  14.4 KB  |  528 lines

  1. Newsgroups: news.answers,comp.unix.shell,comp.unix.questions,comp.unix.programmer
  2. Path: senator-bedfellow.mit.edu!enterpoop.mit.edu!ira.uka.de!math.fu-berlin.de!umn.edu!spool.mu.edu!howland.reston.ans.net!usc!cs.utexas.edu!convex!convex!tchrist
  3. From: Tom Christiansen <tchrist@convex.COM>
  4. Subject: Csh Programming Considered Harmful 
  5. Originator: tchrist@pixel.convex.com
  6. Sender: usenet@news.eng.convex.com (news access account)
  7. Message-ID: <1993Feb11.164948.12450@news.eng.convex.com>
  8. Approved: news-answers-request@MIT.Edu
  9. Date: Thu, 11 Feb 1993 16:49:48 GMT
  10. Expires: Thu, 1 Apr 1993 12:00:00 GMT
  11. Nntp-Posting-Host: pixel.convex.com
  12. Organization: Convex Computer Corporation, Colorado Springs, CO
  13. Followup-To: comp.unix.shell
  14. X-Disclaimer: This message was written by a user at CONVEX Computer
  15.               Corp. The opinions expressed are those of the user and
  16.               not necessarily those of CONVEX.
  17. Lines: 508
  18. Xref: senator-bedfellow.mit.edu news.answers:5912 comp.unix.shell:8853 comp.unix.questions:52936 comp.unix.programmer:9199
  19.  
  20. Archive-name: unix-faq/shell/csh-whynot
  21. Version: $Id: csh-faq,v 1.3 93/02/11 09:45:32 tchrist Exp Locker: tchrist $
  22.  
  23. The following periodic article answers in excruciating detail
  24. the frequently asked question "Why shouldn't I program in csh?".
  25. It is available for anon FTP from convex.com in /pub/csh.whynot .
  26.  
  27.  
  28.            Csh Programming Considered Harmful
  29.  
  30. Resolved: The csh is a tool utterly inadequate for programming, and
  31. its use for such purposes should be strictly banned.
  32.  
  33. I am continually shocked and dismayed to see people write test cases,
  34. install scripts, and other random hackery using the csh.  Lack of
  35. proficiency in the Bourne shell has been known to cause errors in /etc/rc
  36. and .cronrc files, which is a problem, because you *must* write these files
  37. in that language.
  38.  
  39. The csh is seductive because the conditionals are more C-like, so the path
  40. of least resistance if chosen and a csh script is written.  Sadly, this is
  41. a lost cause, and the programmer seldom even realizes it, even when they
  42. find that many simple things they wish to do range from cumbersome to
  43. impossible in the csh.
  44.  
  45.  
  46. FILE DESCRIPTORS
  47.  
  48. The most common problem encountered in csh programming is that
  49. you can't do file-descriptor manipulation.  All you are able to 
  50. do is redirect stdin, or stdout, or dup stderr into stdout. 
  51. Bourne-compatible shells offer you an abundance of more exotic
  52. possibilities.    
  53.  
  54. Writing Files
  55.  
  56. In the Bourne shell, you can open or dup random file descriptors.
  57. For example, 
  58.  
  59.     exec 2>errs.out
  60.  
  61. means that from then on, stderr goes into errs file.
  62.  
  63. Or what if you just want to throw away stderr and leave stdout
  64. alone?    Pretty simple operation, eh?
  65.  
  66.     cmd 2>/dev/null
  67.  
  68. Works in the Bourne shell.  In the csh, you can only make a pitiful 
  69. attempt like this:
  70.  
  71.     (cmd > /dev/tty) >& /dev/null
  72.  
  73. But who said that stdout was my tty?  So it's wrong.  This simple
  74. operation *CANNOT BE DONE* in the csh.
  75.  
  76.  
  77. Along these same lines, you can't direct error messages in csh 
  78. scripts out stderr as is considered proper.  In Bourne shell, you
  79. might say:
  80.  
  81.     echo "$0: cannot find $file" 1>&2
  82.  
  83. but in the csh, you can't redirect stdout out stderr, so you end
  84. up doing something silly like this:
  85.  
  86.     sh -c 'echo "$0: cannot find $file" 1>&2'
  87.  
  88. Reading Files
  89.  
  90. In the csh, all you've got is $<, which reads a line from your tty.  What
  91. if you've redirected stdin?  Tough noogies, you still get your tty, which 
  92. you really can't redirect.  Now, the read statement 
  93. in the Bourne shell allows you to read from stdin, which catches
  94. redirection.  It also means that you can do things like this:
  95.  
  96.     exec 3<file1
  97.     exec 4<file2
  98.  
  99. Now you can read from fd 3 and get lines from file1, or from file2 through
  100. fd 4.   In modern, Bourne-like shells, this suffices: 
  101.  
  102.     read some_var 0<&3
  103.     read another_var 0<&4
  104.  
  105. Although in older ones where read only goes from 0, you trick it:
  106.  
  107.     exec 5<&0  # save old stdin
  108.     exec 0<&3; read some_var
  109.     exec 0<&4; read another_var
  110.     exec 0<&5  # restore it
  111.  
  112.  
  113. Closing FDs
  114.  
  115. In the Bourne shell, you can close file descriptors you don't
  116. want open, like 2>&-, which isn't the same as redirecting it
  117. to /dev/null.
  118.  
  119. More Elaborate Combinations
  120.  
  121. Maybe you want to pipe stderr to a command and leave stdout alone.
  122. Not too hard an idea, right?  You can't do this in the csh as I
  123. mentioned in 1a.  In a Bourne shell, you can do things like this:
  124.  
  125.     exec 3>&1; grep yyy xxx 2>&1 1>&3 3>&- | sed s/file/foobar/ 1>&2 3>&-
  126.     grep: xxx: No such foobar or directory
  127.  
  128. Normal output would be unaffected.  The closes there were in case
  129. something really cared about all its FDs.  We send stderr to sed,
  130. and then put it back out 2.
  131.  
  132. Consider the pipeline:
  133.  
  134.     A | B | C
  135.  
  136. You want to know the status of C, well, that's easy: it's in $?, or
  137. $status in csh.  But if you want it from A, you're out of luck -- if
  138. you're in the csh, that is.  In the Bourne shell, you can get it, although
  139. doing so is a bit tricky.  Here's something I had to do where I ran dd's
  140. stderr into a grep -v pipe to get rid of the records in/out noise, but had
  141. to return the dd's exit status, not the grep's:
  142.  
  143.     device=/dev/rmt8
  144.     dd_noise='^[0-9]+\+[0-9]+ records (in|out)$'
  145.     exec 3>&1
  146.     status=`((dd if=$device ibs=64k 2>&1 1>&3 3>&- 4>&-; echo $? >&4) |
  147.         egrep -v "$dd_noise" 1>&2 3>&- 4>&-) 4>&1`
  148.     exit $status;
  149.  
  150.  
  151.  
  152. COMMAND ORTHOGONALITY
  153.  
  154. Built-ins
  155.  
  156. The csh is a horrid botch with its built-ins.  You can't put them
  157. together in many reasonable way.   Even simple little things like this:    
  158.  
  159.         % time | echo
  160.  
  161. which while nonsensical, shouldn't give me this message:
  162.  
  163.         Reset tty pgrp from 9341 to 26678
  164.  
  165. Others are more fun:
  166.  
  167.         % sleep 1 | while
  168.         while: Too few arguments.
  169.         [5] 9402
  170.         % jobs
  171.         [5]     9402 Done                 sleep |
  172.  
  173.  
  174. Some can even hang your shell.  Try typing ^Z while you're sourcing 
  175. something, or redirecting a source command.  Just make sure you have
  176. another window handy.  Or try 
  177.  
  178.     % history | more
  179.  
  180. on some systems.
  181.  
  182. Flow control
  183.  
  184. You can't mix flow-control and commands, like this:
  185.     
  186.     who | while read line; do
  187.     echo "gotta $line"
  188.     done
  189.  
  190.  
  191. You can't combine multiline constructs in a csh using semicolons.
  192. There's no easy way to do this
  193.  
  194.     alias cmd 'if (foo) then bar; else snark; endif'
  195.  
  196.  
  197. You can't perform redirections with if statements that are
  198. evaluated solely for their exit status:
  199.  
  200.     if ( { grep vt100 /etc/termcap > /dev/null } ) echo ok
  201.  
  202. And even pipes don't work:
  203.  
  204.     if ( { grep vt100 /etc/termcap | sed 's/$/###' } ) echo ok
  205.  
  206. But these work just fine in the Bourne shell:
  207.  
  208.     if grep vt100 /etc/termcap > /dev/null ; then echo ok; fi   
  209.  
  210.     if grep vt100 /etc/termcap | sed 's/$/###/' ; then echo ok; fi
  211.  
  212.  
  213. Stupid parsing bugs
  214.  
  215. Certain reasonable things just don't work, like this:
  216.  
  217.     % kill -1 `cat foo`
  218.     `cat foo`: Ambiguous.
  219.  
  220. But this is ok:
  221.  
  222.     % /bin/kill -1 `cat foo`
  223.  
  224. If you have a stopped job:
  225.  
  226.     [2]     Stopped              rlogin globhost
  227.  
  228. You should be able to kill it with 
  229.  
  230.     % kill %?glob
  231.     kill: No match
  232.  
  233. but
  234.  
  235.     % fg %?glob
  236.  
  237. works.
  238.  
  239. White space can matter:
  240.  
  241.     if(expr)
  242.  
  243. may fail on some versions of csh, while
  244.  
  245.     if (expr)
  246.  
  247. works!
  248.  
  249.  
  250.  
  251. SIGNALS
  252.  
  253. In the csh, all you can do with signals is trap SIGINT.  In the Bourne
  254. shell, you can trap any signal, or the end-of-program exit.    For example,
  255. to blow away a tempfile on any of a variety of signals:
  256.  
  257.     $ trap 'rm -f /usr/adm/tmp/i$$ ;
  258.         echo "ERROR: abnormal exit";
  259.         exit' 1 2 3 15
  260.  
  261.     $ trap 'rm tmp.$$' 0   # on program exit
  262.  
  263.  
  264.  
  265. 6.  QUOTING
  266.  
  267. You can't quote things reasonably in the csh:
  268.  
  269.     set foo = "Bill asked, \"How's tricks?\""
  270.  
  271. doesn't work.  This makes it really hard to construct strings with
  272. mixed quotes in them.  In the Bourne shell, this works just fine. 
  273. In fact, so does this:
  274.  
  275.      cd /mnt; /usr/ucb/finger -m -s `ls \`u\``
  276.  
  277. Dollar signs cannot be escaped in double quotes in the csh.  Ug.
  278.  
  279.     set foo = "this is a \$dollar quoted and this is $HOME not quoted" 
  280.     dollar: Undefined variable.
  281.  
  282. You have to use backslashes for newlines, and it's just darn hard to
  283. get them into strings sometimes.
  284.  
  285.     set foo = "this \
  286.     and that";
  287.     echo $foo
  288.     this  and that
  289.     echo "$foo"
  290.     Unmatched ".  
  291.  
  292. Say what?  You don't have these problems in the Bourne shell, where it's
  293. just fine to write things like this:
  294.  
  295.     echo     'This is 
  296.          some text that contains
  297.          several newlines.'
  298.  
  299.  
  300. As distributed, quoting history references is a challenge.  Consider:
  301.  
  302.     % mail adec23!alberta!pixel.Convex.COM!tchrist
  303.     alberta!pixel.Convex.COM!tchri: Event not found.
  304.  
  305.  
  306. VARIABLE SYNTAX
  307.  
  308. There's this big difference between global (environment) and local
  309. (shell) variables.  In csh, you use a totally different syntax 
  310. to set one from the other.  
  311.  
  312. In Bourne shell, this 
  313.     VAR=foo cmds args
  314.  is the same as
  315.     (export VAR; VAR=foo; cmd args)
  316. or csh's
  317.     (setenv VAR;  cmd args)
  318.  
  319. You can't use :t, :h, etc on envariables.  Watch:
  320.     echo Try testing with $SHELL:t
  321.  
  322. It's really nice to be able to say
  323.     
  324.     ${PAGER-more}
  325. or
  326.     FOO=${BAR:-${BAZ}}
  327.  
  328. to be able to run the user's PAGER if set, and more otherwise.
  329. You can't do this in the csh.  It takes more verbiage.
  330.  
  331. You can't get the process number of the last background command from the
  332. csh, something you might like to do if you're starting up several jobs in
  333. the background.  In the Bourne shell, the pid of the last command put in
  334. the background is available in $!.
  335.  
  336. The csh is also flaky about what it does when it imports an 
  337. environment variable into a local shell variable, as it does
  338. with HOME, USER, PATH, and TERM.  Consider this:
  339.  
  340.     % setenv TERM '`/bin/ls -l / > /dev/tty`'
  341.     % csh -f
  342.  
  343. And watch the fun!
  344.  
  345.  
  346. EXPRESSION EVALUATION
  347.  
  348. Consider this statement in the csh:
  349.  
  350.  
  351.     if ($?MANPAGER) setenv PAGER $MANPAGER
  352.  
  353.  
  354. Despite your attempts to only set PAGER when you want
  355. to, the csh aborts:
  356.  
  357.     MANPAGER: Undefined variable.
  358.  
  359. That's because it parses the whole line anyway AND EVALUATES IT!
  360. You have to write this:
  361.  
  362.     if ($?MANPAGER) then
  363.     setenv PAGER $MANPAGER
  364.     endif
  365.  
  366. That's the same problem you have here:
  367.  
  368.     if ($?X && $X == 'foo') echo ok
  369.     X: Undefined variable
  370.  
  371. This forces you to write a couple nested if statements.  This is highly
  372. undesirable because it renders short-circuit booleans useless in
  373. situations like these.  If the csh were the really C-like, you would
  374. expect to be able to safely employ this kind of logic.  Consider the
  375. common C construct:
  376.  
  377.     if (p && p->member) 
  378.  
  379. Undefined variables are not fatal errors in the Bourne shell, so 
  380. this issue does not arise there.
  381.  
  382. While the csh does have built-in expression handling, it's not
  383. what you might think.  In fact, it's space sensitive.  This is an
  384. error
  385.  
  386.    @ a = 4/2
  387.  
  388. but this is ok
  389.  
  390.    @ a = 4 / 2
  391.  
  392.  
  393. The ad hoc parsing csh employs fouls you up in other places 
  394. as well.  Consider:
  395.  
  396.     % alias foo 'echo hi' ; foo
  397.     foo: Command not found.
  398.     % foo
  399.     hi
  400.  
  401.  
  402. ERROR HANDLING
  403.  
  404. Wouldn't it be nice to know you had an error in your script before
  405. you ran it?   That's what the -n flag is for: just check the syntax.
  406. This is especially good to make sure seldom taken segments of code
  407. code are correct.  Alas, the csh implementation of this doesn't work.
  408. Consider this statement:
  409.  
  410.     exit (i)
  411.  
  412. Of course, they really meant
  413.  
  414.     exit (1)
  415.  
  416. or just
  417.  
  418.     exit 1
  419.  
  420. Either shell will complain about this.  But if you hide this in an if
  421. clause, like so:
  422.  
  423.     #!/bin/csh -fn
  424.     if (1) then
  425.     exit (i)
  426.     endif
  427.  
  428. The csh tells you there's nothing wrong with this script.  The equivalent
  429. construct in the Bourne shell, on the other hand, tells you this:
  430.  
  431.  
  432.     #!/bin/sh -n
  433.     if (1) then
  434.     exit (i)
  435.     endif
  436.  
  437.     /tmp/x: syntax error at line 3: `(' unexpected
  438.  
  439.  
  440.  
  441. RANDOM BUGS
  442.  
  443. Here's one:
  444.  
  445.     fg %?string
  446.     ^Z
  447.     kill  %?string
  448.     No match.
  449.  
  450. Huh? Here's another
  451.  
  452.     !%s%x%s
  453.  
  454. Coredump, or garbage.
  455.  
  456. If you have an alias with backquotes, and use that in backquotes in 
  457. another one, you get a coredump.
  458.  
  459. Try this:
  460.     % repeat 3 echo "/vmu*"
  461.     /vmu*
  462.     /vmunix
  463.     /vmunix
  464. What???
  465.  
  466.  
  467. Here's another one:
  468.  
  469.     % mkdir tst
  470.     % cd tst
  471.     % touch '[foo]bar'
  472.     % foreach var ( * )
  473.     > echo "File named $var"
  474.     > end
  475.     foreach: No match.
  476.  
  477.  
  478. SUMMARY
  479.  
  480.  
  481. While some vendors have fixed some of the csh's bugs (the tcsh also does
  482. much better here), many have added new ones.  Most of its problems can
  483. never be solved because they're not actually bugs per se, but rather the
  484. direct consequences of braindead design decisions.  It's inherently flawed.
  485.  
  486. Do yourself a favor, and if you *have* to write a shell script, do it in the 
  487. Bourne shell.  It's on every UNIX system out there.  However, behavior 
  488. can vary.
  489.  
  490. There are other possibilities.
  491.  
  492. The Korn shell is the preferred programming shell by many sh addicts,
  493. but it still suffers from inherent problems in the Bourne shell's design,
  494. such as parsing and evaluation horrors.  The Korn shell or its
  495. public-domain clones and supersets (like bash) aren't quite so ubiquitous
  496. as sh, so it probably wouldn't be wise to write a sharchive in them that
  497. you post to the net.  When 1003.2 becomes a real standard that companies
  498. are forced to adhere to, then we'll be in much better shape.  Until
  499. then, we'll be stuck with bug-incompatible versions of the sh lying about.
  500.  
  501. The Plan 9 shell, rc, is much cleaner in its parsing and evaluation; it is
  502. not widely available, so you'd be sacrificing portability.  No vendor
  503. is shipping it yet.
  504.  
  505. If you don't have to use a shell, but just want an interpreted language,
  506. many other free possibilities present themselves, like Perl, REXX, TCL,
  507. Scheme, or Python.  Of these, Perl is probably the most widely available
  508. on UNIX (and many other) systems and certainly comes with the most
  509. extensive UNIX interface.  Some vendors even ship it standard.
  510.  
  511. If you have a problem that would ordinarily use sed or awk or sh, but it
  512. exceeds their capabilities or must run a little faster, and you don't want
  513. to write the silly thing in C, then Perl may be for you.  You can get
  514. at networking functions, binary data, and most ofthe C library. There
  515. are also translators to turn your sed and awk scripts into Perl scripts,
  516. as well as a symbolic debugger.  Tchrist's personal rule of thumb is
  517. that if it's the size that fits in a Makefile, it gets written in the
  518. Bourne shell, but anything bigger gets written in Perl.
  519.  
  520. See the comp.lang.{perl,rexx,tcl} newsgroups for details about these
  521. languages (including FAQs), or David Muir Sharnoff's comparison of 
  522. freely available languages and tools in comp.lang.misc and news.answers.
  523. -- 
  524.     Tom Christiansen      tchrist@convex.com      convex!tchrist
  525.  
  526.         "There is no reason for any individual to have a computer in their
  527.          home."  (Ken Olson, President, Digital Equipment, 1977)
  528.